/*
 * FindBugs - Find Bugs in Java programs
 * Copyright (C) 2003-2007 University of Maryland
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package edu.umd.cs.findbugs.classfile.engine.bcel;

import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;

import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.GETSTATIC;
import org.apache.bcel.generic.IFNE;
import org.apache.bcel.generic.Instruction;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.MethodGen;

import edu.umd.cs.findbugs.SystemProperties;
import edu.umd.cs.findbugs.ba.AnalysisContext;
import edu.umd.cs.findbugs.ba.AnalysisFeatures;
import edu.umd.cs.findbugs.ba.BasicBlock;
import edu.umd.cs.findbugs.ba.CFG;
import edu.umd.cs.findbugs.ba.CFGBuilder;
import edu.umd.cs.findbugs.ba.CFGBuilderFactory;
import edu.umd.cs.findbugs.ba.ClassContext;
import edu.umd.cs.findbugs.ba.DataflowAnalysisException;
import edu.umd.cs.findbugs.ba.DepthFirstSearch;
import edu.umd.cs.findbugs.ba.Edge;
import edu.umd.cs.findbugs.ba.EdgeTypes;
import edu.umd.cs.findbugs.ba.JavaClassAndMethod;
import edu.umd.cs.findbugs.ba.MethodUnprofitableException;
import edu.umd.cs.findbugs.ba.MissingClassException;
import edu.umd.cs.findbugs.ba.PruneInfeasibleExceptionEdges;
import edu.umd.cs.findbugs.ba.PruneUnconditionalExceptionThrowerEdges;
import edu.umd.cs.findbugs.ba.SignatureConverter;
import edu.umd.cs.findbugs.ba.type.TypeDataflow;
import edu.umd.cs.findbugs.classfile.CheckedAnalysisException;
import edu.umd.cs.findbugs.classfile.Global;
import edu.umd.cs.findbugs.classfile.IAnalysisCache;
import edu.umd.cs.findbugs.classfile.MethodDescriptor;

/**
 * Analysis engine to produce CFG (control flow graph) objects for an analyzed method.
 * 
 * @author David Hovemeyer
 */
public class CFGFactory extends AnalysisFactory<CFG> {
	private static final boolean DEBUG_CFG = SystemProperties.getBoolean("classContext.debugCFG");

	/**
	 * Constructor.
	 */
	public CFGFactory() {
		super("control flow graph factory", CFG.class);
	}

	/* (non-Javadoc)
	 * @see edu.umd.cs.findbugs.classfile.IAnalysisEngine#analyze(edu.umd.cs.findbugs.classfile.IAnalysisCache, java.lang.Object)
	 */
	public CFG analyze(IAnalysisCache analysisCache, MethodDescriptor descriptor) throws CheckedAnalysisException {
		// Construct the CFG in its raw form
		MethodGen methodGen = analysisCache.getMethodAnalysis(MethodGen.class, descriptor);
		if (methodGen == null) {
			JavaClass jclass = analysisCache.getClassAnalysis(JavaClass.class, descriptor.getClassDescriptor());
			Method method = analysisCache.getMethodAnalysis(Method.class, descriptor);
			JavaClassAndMethod javaClassAndMethod = new JavaClassAndMethod(jclass, method);
			AnalysisContext.currentAnalysisContext().getLookupFailureCallback().reportSkippedAnalysis(descriptor);
			throw new MethodUnprofitableException(javaClassAndMethod);
		}
		CFGBuilder cfgBuilder = CFGBuilderFactory.create(methodGen);
		cfgBuilder.build();
		CFG cfg = cfgBuilder.getCFG();

		// Mark as busy while we're pruning the CFG.
		cfg.setFlag( CFG.BUSY);

		// Important: eagerly put the CFG in the analysis cache.
		// Recursively performed analyses required to prune the CFG,
		// such as TypeAnalysis, will operate on the raw CFG.
		analysisCache.eagerlyPutMethodAnalysis(CFG.class, descriptor, cfg);

		// Record method name and signature for informational purposes
		cfg.setMethodName(SignatureConverter.convertMethodSignature(methodGen));
		cfg.setMethodGen(methodGen);

		// System.out.println("CC: getting refined CFG for " + methodId);
		if (CFGFactory.DEBUG_CFG) {
			String methodId = methodGen.getClassName() + "." + methodGen.getName() + ":" + methodGen.getSignature();
			ClassContext.indent();
			System.out.println("CC: getting refined CFG for " + methodId);
		}
		if (ClassContext.DEBUG) {
			String methodId = methodGen.getClassName() + "." + methodGen.getName() + ":" + methodGen.getSignature();
			System.out.println("ClassContext: request to prune " + methodId);
		}

		// Remove CFG edges corresponding to failed assertions.
		boolean changed = false;
		boolean ASSUME_ASSERTIONS_ENABLED = true;
		if (ASSUME_ASSERTIONS_ENABLED) {
			LinkedList<Edge> edgesToRemove = new LinkedList<Edge>();
			for (Iterator<Edge> i = cfg.edgeIterator(); i.hasNext();) {
				Edge e = i.next();
				if (e.getType() == EdgeTypes.IFCMP_EDGE) {
					try {
						BasicBlock source = e.getSource();
						InstructionHandle last = source
						.getLastInstruction();
						Instruction lastInstruction = last.getInstruction();
						InstructionHandle prev = last.getPrev();
						Instruction prevInstruction = prev.getInstruction();
						if (prevInstruction instanceof GETSTATIC
								&& lastInstruction instanceof IFNE) {
							GETSTATIC getStatic = (GETSTATIC) prevInstruction;

							if (getStatic.getFieldName(
									methodGen.getConstantPool()).equals(
											"$assertionsDisabled")
											&& getStatic.getSignature(
													methodGen.getConstantPool())
													.equals("Z"))
								edgesToRemove.add(e);
						}
					} catch (RuntimeException exception) {
						assert true; // ignore it
					}
				}
			}
			if (edgesToRemove.size() > 0) {
				changed = true;
				for (Edge e : edgesToRemove) {
					cfg.removeEdge(e);
				}
			}
		}
		cfg.setFlag(CFG.PRUNED_FAILED_ASSERTION_EDGES);

		final boolean PRUNE_INFEASIBLE_EXCEPTION_EDGES =
			AnalysisContext.currentAnalysisContext().getBoolProperty(AnalysisFeatures.ACCURATE_EXCEPTIONS);

		if (PRUNE_INFEASIBLE_EXCEPTION_EDGES && !cfg.isFlagSet(CFG.PRUNED_INFEASIBLE_EXCEPTIONS)) {
			try {
				TypeDataflow typeDataflow = analysisCache.getMethodAnalysis(TypeDataflow.class, descriptor);
				// Exception edge pruning based on ExceptionSets.
				// Note: this is quite slow.
				PruneInfeasibleExceptionEdges pruner =
					new PruneInfeasibleExceptionEdges(cfg, methodGen, typeDataflow);
				pruner.execute();
				changed  = changed || pruner.wasCFGModified();
			} catch (MissingClassException e) {
				AnalysisContext.currentAnalysisContext().getLookupFailureCallback().reportMissingClass(e.getClassNotFoundException());
			} catch (DataflowAnalysisException e) {
				AnalysisContext.currentAnalysisContext().getLookupFailureCallback().logError("unable to extract type analysis", e);
			} catch (ClassNotFoundException e) {
				AnalysisContext.currentAnalysisContext().getLookupFailureCallback().reportMissingClass(e);
			}
		}
		cfg.setFlag( CFG.PRUNED_INFEASIBLE_EXCEPTIONS);

		final boolean PRUNE_UNCONDITIONAL_EXCEPTION_THROWER_EDGES =
			!AnalysisContext.currentAnalysisContext().getBoolProperty(AnalysisFeatures.CONSERVE_SPACE);

		if (PRUNE_UNCONDITIONAL_EXCEPTION_THROWER_EDGES && !cfg.isFlagSet(CFG.PRUNED_UNCONDITIONAL_THROWERS)) {
			try {
				JavaClass jclass = analysisCache.getClassAnalysis(JavaClass.class, descriptor.getClassDescriptor());
				Method method = analysisCache.getMethodAnalysis(Method.class, descriptor);
				ConstantPoolGen cpg = analysisCache.getClassAnalysis(ConstantPoolGen.class, descriptor.getClassDescriptor());
				TypeDataflow typeDataflow = analysisCache.getMethodAnalysis(TypeDataflow.class, descriptor);

				PruneUnconditionalExceptionThrowerEdges pruner =
					new PruneUnconditionalExceptionThrowerEdges(
							jclass,
							method,
							methodGen,
							cfg,
							cpg,
							typeDataflow,
							AnalysisContext.currentAnalysisContext());
				pruner.execute();
				if (pruner.wasCFGModified()) {
					changed = true;

				}
			} catch (DataflowAnalysisException e) {
				AnalysisContext.logError("Error pruning normal return edges for unconditionally throwing methods for " + descriptor, e);
			}
		}
		cfg.setFlag( CFG.PRUNED_UNCONDITIONAL_THROWERS);

		// Now we are done with the CFG refining process
		cfg.setFlag(CFG.REFINED);
		cfg.clearFlag(CFG.BUSY);

		// If the CFG changed as a result of pruning, purge all analysis results
		// for the method.
		if (changed) {
			
			DepthFirstSearch dfs = new DepthFirstSearch(cfg);
			dfs.search();
			Collection<BasicBlock> unreachable = dfs.unvisitedVertices();
			if (!unreachable.isEmpty()) {
				if (DEBUG_CFG) System.out.println("Unreachable blocks");
				for(BasicBlock b : unreachable) {
					if (DEBUG_CFG) System.out.println(" removing " + b);
					cfg.removeVertex(b);
				}
			}
			Global.getAnalysisCache().purgeMethodAnalyses(descriptor);
		}

		return cfg;
	}

	/* (non-Javadoc)
	 * @see edu.umd.cs.findbugs.classfile.IAnalysisEngine#registerWith(edu.umd.cs.findbugs.classfile.IAnalysisCache)
	 */
	@Override
    public void registerWith(IAnalysisCache analysisCache) {
		analysisCache.registerMethodAnalysisEngine(CFG.class, this);
	}
}
